2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
33 #import <utilities/debugging.h>
34 #import <utilities/SecCFWrappers.h>
35 #import <utilities/SecXPCError.h>
36 #import <os/variant_private.h>
38 #import <Accounts/Accounts.h>
39 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
40 #import <AOSAccounts/MobileMePrefsCore.h>
41 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
42 #import <AOSAccounts/iCloudAccount.h>
44 #include <msgtracer_client.h>
45 #include <msgtracer_keys.h>
46 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
47 #import <ProtectedCloudStorage/CloudIdentity.h>
48 #import "CoreCDP/CDPFollowUpController.h"
49 #import "CoreCDP/CDPFollowUpContext.h"
50 #import <CoreCDP/CDPAccount.h>
52 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
53 static const NSString * const kKickedOutKey = @"KickedOut";
54 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
55 static const NSString * const kPasswordChangedOrTrustedDeviceChanged = @"TDorPasswordChanged";
56 static NSString *prefpane = @"/System/Library/PreferencePanes/iCloudPref.prefPane";
57 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
58 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
59 static NSString *KeychainPCDetailsAEAction = @"AKPCDetailsAEAction";
60 bool _hasPostedAndStillInError = false;
61 bool _haveCheckedForICDPStatusOnceInCircle = false;
62 bool _isAccountICDP = false;
64 @implementation KNAppDelegate
66 static NSUserNotificationCenter *appropriateNotificationCenter()
68 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
69 type: _NSUserNotificationCenterTypeSystem];
72 static BOOL isErrorFromXPC(CFErrorRef error)
74 // Error due to XPC failure does not provide information about the circle.
75 if (error && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(error)))) {
81 static void PSKeychainSyncIsUsingICDP(void)
83 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
84 ACAccount *primaryiCloudAccount = nil;
86 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
87 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
90 NSString *dsid = primaryiCloudAccount.icaPersonID;
91 BOOL isICDPEnabled = NO;
93 isICDPEnabled = [CDPAccount isICDPEnabledForDSID:dsid];
94 NSLog(@"iCDP: PSKeychainSyncIsUsingICDP returning %@", isICDPEnabled ? @"TRUE" : @"FALSE");
96 NSLog(@"iCDP: no primary account");
99 _isAccountICDP = isICDPEnabled;
102 -(void) startFollowupKitRepair
104 NSError *localError = NULL;
105 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
106 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
108 secnotice("followup", "Posting a follow up (for SOS) of type repair");
109 [cdpd postFollowUpWithContext:context error:&localError ];
111 secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError);
114 secnotice("kcn", "CoreCDP handling follow up");
115 _hasPostedAndStillInError = false;
119 - (void) handleDismissedNotification
122 secnotice("kcn", "handling dismissed notification, would start a follow up");
123 [self startFollowupKitRepair];
126 secerror("unable to find primary account");
129 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
131 if (eventName == nil)
134 secnotice("kcn", "notifyiCloudPreferencesAbout %@", eventName);
136 NSString *accountID = (__bridge_transfer NSString*)(MMCopyLoggedInAccountFromAccounts());
137 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
138 ACAccount *primaryiCloudAccount = nil;
140 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
141 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
144 if(primaryiCloudAccount){
146 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, accountID, &aeDesc);
148 NSArray *prefPaneURL = [NSArray arrayWithObject: [NSURL fileURLWithPath: prefpane ]];
150 LSLaunchURLSpec lsSpec = {
152 .itemURLs = (__bridge CFArrayRef)prefPaneURL,
153 .passThruParams = &aeDesc,
154 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
158 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
161 secerror("Can't send event %@, err=%d", eventName, err);
162 AEDisposeDesc(&aeDesc);
164 secerror("unable to create and send aedesc for account: '%@' and action: '%@'\n", primaryiCloudAccount, eventName);
167 secerror("unable to find primary account");
172 NSDate *nowish = [NSDate new];
174 self.state = [KNPersistentState loadFromStorage];
175 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
176 secnotice("kcn", "REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
178 // self.circle.rawStatus might not be valid yet
179 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
180 // Still have a request pending, send reminder, and also in addtion to the UI
181 // we need to send a notification for iCloud pref pane to pick up
182 CFNotificationCenterPostNotificationWithOptions(
183 CFNotificationCenterGetDistributedCenter(),
184 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
185 (__bridge const void *) [self.state.applicationDate description], NULL, 0
188 [self postApplicationReminder];
189 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
190 [self.state writeToStorage];
196 - (void) scheduleActivityAt: (NSDate *) time
198 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
199 NSTimeInterval howSoon = [time timeIntervalSinceNow];
201 [self scheduleActivityIn:ceil(howSoon)];
208 - (void) scheduleActivityIn: (int) alertInterval
210 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
211 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
212 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
213 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
214 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
215 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
217 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
223 - (NSTimeInterval) getPendingApplicationReminderInterval
225 if (self.state.pendingApplicationReminderInterval)
226 return [self.state.pendingApplicationReminderInterval doubleValue];
231 - (NSMutableSet *) makeApplicantSet {
232 KNAppDelegate *me = self;
233 NSMutableSet *applicantIds = [NSMutableSet new];
234 for (KDCirclePeer *applicant in me.circle.applicants) {
235 [me postForApplicant:applicant];
236 [applicantIds addObject:applicant.idString];
241 - (bool) removeAllNotificationsOfType: (NSString *) typeString {
242 bool didRemove = false;
243 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
244 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
245 if (note.userInfo[typeString]) {
246 [appropriateNotificationCenter() removeDeliveredNotification: note];
253 static const char *sosCCStatusCString(SOSCCStatus status) {
255 case kSOSCCError: return "kSOSCCError";
256 case kSOSCCInCircle: return "kSOSCCInCircle";
257 case kSOSCCNotInCircle: return "kSOSCCNotInCircle";
258 case kSOSCCCircleAbsent: return "kSOSCCCircleAbsent";
259 case kSOSCCRequestPending: return "kSOSCCRequestPending";
260 default: return "unknown";
264 static const char *sosDepartureReasonCString(enum DepartureReason departureReason){
265 switch(departureReason) {
266 case kSOSDepartureReasonError: return "kSOSDepartureReasonError";
267 case kSOSNeverLeftCircle: return "kSOSNeverLeftCircle";
268 case kSOSWithdrewMembership: return "kSOSWithdrewMembership";
269 case kSOSMembershipRevoked: return "kSOSMembershipRevoked";
270 case kSOSLeftUntrustedCircle: return "kSOSLeftUntrustedCircle";
271 case kSOSNeverAppliedToCircle: return "kSOSNeverAppliedToCircle";
272 case kSOSDiscoveredRetirement: return "kSOSDiscoveredRetirement";
273 case kSOSLostPrivateKey: return "kSOSLostPrivateKey";
274 default: return "unknown reason";
279 - (void) processCircleState {
280 CFErrorRef err = NULL;
281 KNAppDelegate *me = self;
283 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
284 if (isErrorFromXPC(err)) {
285 secnotice("kcn", "SOSCCGetLastDepartureReason failed with xpc error: %@", err);
289 secnotice("kcn", "SOSCCGetLastDepartureReason failed with: %@", err);
293 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&err);
294 if (isErrorFromXPC(err)) {
295 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with xpc error: %@", err);
299 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
303 NSDate *nowish = [NSDate date];
304 me.state = [KNPersistentState loadFromStorage];
305 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
307 PSKeychainSyncIsUsingICDP();
309 secnotice("kcn", "processCircleState starting ICDP: %s SOSCCStatus: %s DepartureReason: %s",
310 (_isAccountICDP) ? "Enabled": "Disabled",
311 sosCCStatusCString(circleStatus),
312 sosDepartureReasonCString(departureReason));
315 me.state.lastCircleStatus = circleStatus;
316 [me.state writeToStorage];
318 switch(circleStatus) {
320 secnotice("kcn", "iCDP: device is in circle!");
321 _hasPostedAndStillInError = false;
323 case kSOSCCRequestPending:
324 [me scheduleActivityAt:me.state.pendingApplicationReminder];
326 case kSOSCCCircleAbsent:
327 case kSOSCCNotInCircle:
328 [me outOfCircleAlert: departureReason];
329 secnotice("kcn", "{ChangeCallback} Pending request START");
330 me.state.applicationDate = nowish;
331 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
332 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
333 [me scheduleActivityAt:me.state.pendingApplicationReminder];
337 You would think we could count on not being iCDP if the account was signed out. Evidently that's wrong.
338 So we'll go based on the artifact that when the account object is reset (like by signing out) the
339 departureReason will be set to kSOSDepartureReasonError. So we won't push to get back into a circle if that's
340 the current reason. I've checked code for other ways we could be out. If we boot and can't load the account
341 we'll end up with kSOSDepartureReasonError. Then too if we end up in kSOSDepartureReasonError and reboot we end up
342 in the same place. Leave it to cdpd to decide whether the user needs to sign in to an account.
344 if(departureReason != kSOSDepartureReasonError) {
345 secnotice("kcn", "ICDP: We need the password to initiate trust");
346 [me postRequirePassword];
347 _hasPostedAndStillInError = true;
349 secnotice("kcn", "iCDP: We appear to not be associated with an iCloud account");
353 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
356 } else { // SA version
357 switch(circleStatus) {
359 secnotice("kcn", "SA: device is in circle!");
360 _hasPostedAndStillInError = false;
362 case kSOSCCRequestPending:
363 [me scheduleActivityAt:me.state.pendingApplicationReminder];
364 secnotice("kcn", "{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
366 case kSOSCCCircleAbsent:
367 case kSOSCCNotInCircle:
368 switch (departureReason) {
369 case kSOSDiscoveredRetirement:
370 case kSOSLostPrivateKey:
371 case kSOSWithdrewMembership:
372 case kSOSNeverAppliedToCircle:
373 case kSOSDepartureReasonError:
374 case kSOSMembershipRevoked:
376 if(me.state.lastCircleStatus == kSOSCCInCircle) {
377 secnotice("kcn", "SA: circle status went from in circle to %s: reason: %s", sosCCStatusCString(circleStatus), sosDepartureReasonCString(departureReason));
381 case kSOSNeverLeftCircle:
382 case kSOSLeftUntrustedCircle:
383 [me outOfCircleAlert: departureReason];
384 secnotice("kcn", "{ChangeCallback} Pending request START");
385 me.state.applicationDate = nowish;
386 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
387 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
388 [me scheduleActivityAt:me.state.pendingApplicationReminder];
393 if(me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
394 secnotice("kcn", "SA: circle status went from in circle to error - we need the password");
395 [me postRequirePassword];
396 _hasPostedAndStillInError = true;
400 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
406 // Circle applications: pending request(s) started / completed
407 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
408 secnotice("kcn", "Pending request completed");
409 me.state.applicationDate = [NSDate distantPast];
410 me.state.pendingApplicationReminder = [NSDate distantFuture];
411 [me.state writeToStorage];
414 if([me removeAllNotificationsOfType: @"ApplicationReminder"]) {
415 secnotice("kcn", "{ChangeCallback} removed application remoinders");
419 // Clear out (old) reset notifications
420 if (circleStatus == kSOSCCInCircle) {
421 secnotice("kcn", "{ChangeCallback} kSOSCCInCircle");
422 if([me removeAllNotificationsOfType: (NSString*) kValidOnlyOutOfCircleKey]) {
423 secnotice("kcn", "Removed existing notifications now that we're in circle");
425 if([me removeAllNotificationsOfType: (NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
426 secnotice("kcn", "Removed existing password notifications now that we're in circle");
430 secnotice("kcn", "{ChangeCallback} Applicants");
431 NSMutableSet *applicantIds = [me makeApplicantSet];
432 // Clear applicant notifications that aren't pending any more
433 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
434 secnotice("kcn", "Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
435 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
436 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
437 secnotice("kcn", "No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
438 [notificationCenter removeDeliveredNotification:note];
440 secnotice("kcn", "Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
443 } else { // Clear any pending applicant notifications since we aren't in circle or invalid
444 if([me removeAllNotificationsOfType: (NSString*) @"applicantId"]) {
445 secnotice("kcn", "Not in circle or invalid - removed applicant notes");
449 me.state.lastCircleStatus = circleStatus;
450 [me.state writeToStorage];
453 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
455 appropriateNotificationCenter().delegate = self;
458 secnotice("kcn", "Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
460 notify_register_dispatch(kPublicKeyAvailable, &available, dispatch_get_main_queue(), ^(int token) {
461 CFErrorRef err = NULL;
462 KNAppDelegate *me = self;
463 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
465 if (isErrorFromXPC(err)) {
466 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
472 me.state = [KNPersistentState loadFromStorage];
474 secnotice("kcn", "got public key available notification");
475 me.state.lastCircleStatus = currentCircleStatus;
476 [me.state writeToStorage];
479 //register for public key not available notification, if occurs KCN can react
480 notify_register_dispatch(kPublicKeyNotAvailable, &out_taken, dispatch_get_main_queue(), ^(int token) {
481 secnotice("kcn", "got public key not available notification");
482 KNAppDelegate *me = self;
483 [me processCircleState];
486 self.viewedIds = [NSMutableSet new];
487 self.circle = [KDSecCircle new];
488 KNAppDelegate *me = self;
490 [self.circle addChangeCallback:^{
491 secnotice("kcn", "{ChangeCallback}");
492 [me processCircleState];
497 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
498 shouldPresentNotification: (NSUserNotification *) notification
504 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
505 didActivateNotification: (NSUserNotification *) notification
507 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
508 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
513 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
514 didDismissAlert: (NSUserNotification *) notification
516 [self handleDismissedNotification];
518 // If we don't do anything here & another notification comes in we
519 // will repost the alert, which will be dumb.
520 id applicantId = notification.userInfo[@"applicantId"];
521 if (applicantId != nil) {
522 [self.viewedIds addObject:applicantId];
527 - (void) postForApplicant: (KDCirclePeer *) applicant
529 static int postCount = 0;
531 if ([self.viewedIds containsObject:applicant.idString]) {
532 secnotice("kcn", "Already viewed %@, skipping", applicant);
536 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
537 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
538 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
539 if (note.isPresented) {
540 secnotice("kcn", "Already posted&presented: %@ (I=%@)", note, note.userInfo);
543 secnotice("kcn", "Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
548 NSUserNotification *note = [NSUserNotification new];
549 note.title = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE), applicant.name];
550 note.informativeText = [KNAppDelegate localisedApprovalBodyWithDeviceTypeFromPeerInfo:applicant.peerObject];
551 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
552 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
553 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
554 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE);
555 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE);
556 note.identifier = [[NSUUID new] UUIDString];
558 @"applicantName": applicant.name,
559 @"applicantId" : applicant.idString,
560 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
563 secnotice("kcn", "About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
564 [appropriateNotificationCenter() deliverNotification:note];
565 [self.viewedIds addObject:applicant.idString];
569 + (NSString *)localisedApprovalBodyWithDeviceTypeFromPeerInfo:(id)peerInfo {
570 NSString *type = (__bridge NSString *)SOSPeerInfoGetPeerDeviceType((__bridge SOSPeerInfoRef)(peerInfo));
571 CFStringRef localisedType = NULL;
572 if ([type isEqualToString:@"iPad"]) {
573 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPAD);
574 } else if ([type isEqualToString:@"iPhone"]) {
575 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPHONE);
576 } else if ([type isEqualToString:@"iPod"]) {
577 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPOD);
578 } else if ([type isEqualToString:@"Mac"]) {
579 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_MAC);
581 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_GENERIC);
583 return (__bridge_transfer NSString *)localisedType;
586 - (void) postRequirePassword
588 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(NULL);
589 if(currentCircleStatus != kSOSCCError) {
590 secnotice("kcn", "postRequirePassword when not needed");
594 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(NULL);
597 secnotice("kcn","would have posted needs password and then followed up");
598 [self startFollowupKitRepair];
599 } else if(departureReason == kSOSNeverLeftCircle) { // The only SA case for prompting
600 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
601 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
602 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
603 if (note.isPresented) {
604 secnotice("kcn", "Already posted & presented: %@", note);
605 [appropriateNotificationCenter() removeDeliveredNotification: note];
607 secnotice("kcn", "Already posted, but not presented: %@", note);
612 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
613 if (os_variant_has_internal_ui("iCloudKeychain")) {
614 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), "Device became untrusted or password changed"];
615 message = [message stringByAppendingString: reason_str];
618 NSUserNotification *note = [NSUserNotification new];
619 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
620 note.informativeText = message;
621 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
622 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
623 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
624 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
625 note.identifier = [[NSUUID new] UUIDString];
628 kPasswordChangedOrTrustedDeviceChanged : @1,
629 @"Activate" : (__bridge NSString *) kMMPropertyKeychainPCDetailsAEAction,
632 secnotice("kcn", "body=%@", note.informativeText);
633 secnotice("kcn", "About to post #-/%lu (PASSWORD/TRUSTED DEVICE): %@", noteCenter.deliveredNotifications.count, note);
634 [appropriateNotificationCenter() deliverNotification:note];
636 secnotice("kcn", "postRequirePassword when not needed for SA");
640 - (void) outOfCircleAlert: (int) reason
644 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
645 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
646 if (note.userInfo[(NSString*) kKickedOutKey]) {
647 if (note.isPresented) {
648 secnotice("kcn", "Already posted&presented (removing): %@", note);
649 [appropriateNotificationCenter() removeDeliveredNotification: note];
651 secnotice("kcn", "Already posted, but not presented: %@", note);
656 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
657 if (os_variant_has_internal_ui("iCloudKeychain")) {
658 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), sosDepartureReasonCString(reason)];
659 message = [message stringByAppendingString: reason_str];
662 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
663 // Contrary to HI spec (and I think it makes more sense)
664 // 1. otherButton == top : Not Now
665 // 2. actionButton == bottom: Continue
666 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
667 NSUserNotification *note = [NSUserNotification new];
668 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
669 note.informativeText = message;
670 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
671 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
672 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
673 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
674 note.identifier = [[NSUUID new] UUIDString];
678 kValidOnlyOutOfCircleKey: @1,
679 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
682 secnotice("kcn", "body=%@", note.informativeText);
683 secnotice("kcn", "About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
684 [appropriateNotificationCenter() deliverNotification:note];
688 secnotice("kcn","outOfCircleAlert starting followup repair");
689 [self startFollowupKitRepair];
693 - (void) postApplicationReminder
695 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
696 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
697 if (note.userInfo[@"ApplicationReminder"]) {
698 if (note.isPresented) {
699 secnotice("kcn", "Already posted&presented (removing): %@", note);
700 [appropriateNotificationCenter() removeDeliveredNotification: note];
702 secnotice("kcn", "Already posted, but not presented: %@", note);
707 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
708 // Contrary to HI spec (and I think it makes more sense)
709 // 1. otherButton == top : Not Now
710 // 2. actionButton == bottom: Continue
711 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
712 NSUserNotification *note = [NSUserNotification new];
713 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
714 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
715 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
716 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
717 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
718 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
719 note.identifier = [[NSUUID new] UUIDString];
722 @"ApplicationReminder" : @1,
723 kValidOnlyOutOfCircleKey: @1,
724 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
727 secnotice("kcn", "About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
728 [appropriateNotificationCenter() deliverNotification:note];